This R script is used to validate the points in the ReSurvey database
using RS indicators (indices + phenology + canopy height).
Distributions all bioregions
Indices
# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
ggplot(data %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = !!sym(x_var))) +
geom_histogram(color = "black", fill = "white") +
labs(x = x_label, y = "Frequency") +
theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
for (i in seq_along(y_vars)) {
y_var <- y_vars[[i]]
y_label <- y_labels[[i]]
p <- ggplot(data = data %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
label = length(x)),
geom = "text", aes(label = ..label..), vjust = 0.5) +
labs(y = y_label, x = "EUNIS level 1") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
guides(fill = FALSE, color = FALSE) +
theme_bw() + coord_flip()
print(p)
}
}
Ranges of min and max:
range(data_validation$NDVI_max, na.rm = T) # NDVI_max > 1 (slightly)
[1] 0.06446001 1.09902698
range(data_validation$NDMI_max, na.rm = T) # NDMI_max > 1 (slightly)
[1] -0.1994688 1.0924980
range(data_validation$NDWI_max, na.rm = T)
[1] -0.7283135 0.6050488
range(data_validation$SAVI_max, na.rm = T)
[1] 0.01849304 0.85933671
range(data_validation$EVI_max, na.rm = T) # EVI_max > 1 (slightly)
[1] 0.05318326 1.14645898
range(data_validation$NDVI_min, na.rm = T)
[1] -0.6891069 0.8422586
range(data_validation$NDMI_min, na.rm = T)
[1] -0.6620149 0.5395376
range(data_validation$NDWI_min, na.rm = T) # NDWI_min < -1 (slightly)
[1] -1.0579643 -0.0262825
range(data_validation$SAVI_min, na.rm = T)
[1] -0.4959691 0.6207103
range(data_validation$EVI_min, na.rm = T) # EVI_min < -1!
[1] -4.1368512 0.6563168
nrow(data_validation %>% dplyr::filter(NDVI_max > 1))
[1] 12
nrow(data_validation %>% dplyr::filter(NDMI_max > 1))
[1] 70
nrow(data_validation %>% dplyr::filter(EVI_max > 1))
[1] 5
nrow(data_validation %>% dplyr::filter(NDWI_min < -1))
[1] 11
nrow(data_validation %>% dplyr::filter(EVI_min < -1))
[1] 15
Histograms to check that max and min values are ok:
plot_histogram(data_validation, "NDVI_max", "NDVI max")

plot_histogram(data_validation, "NDMI_max", "NDMI max")

plot_histogram(data_validation, "NDWI_max", "NDWI max")

plot_histogram(data_validation, "SAVI_max", "SAVI max")

plot_histogram(data_validation, "EVI_max", "EVI max")

plot_histogram(data_validation, "NDVI_min", "NDVI min")

plot_histogram(data_validation, "NDMI_min", "NDMI min")

plot_histogram(data_validation, "NDWI_min", "NDWI min")

plot_histogram(data_validation, "SAVI_min", "SAVI min")

plot_histogram(data_validation %>%
dplyr::filter(EVI_min > -1.5), "EVI_min", "EVI min")

nrow(data_validation %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
dplyr::filter(EVI_max > 1 | EVI_max < -1))
[1] 5
data_validation %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
dplyr::filter(EVI_max > 1 | EVI_max < -1) %>%
count(biogeo, unit)
Most EVI values are ok!
Distribution plots:
distr_plot(data_validation %>% dplyr::filter(EVI_min > -0.5),
c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
"NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"),
c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
"NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"))










CH
distr_plot(data_validation, "canopy_height", "Canopy height (m)")

Show habitats with CH categories
ggplot(data_validation %>%
# Keep only forests, grasslands, shrublands and wetlands
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
mutate(CH_cat =
factor(
case_when(canopy_height == 0 ~ "0 m",
canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
canopy_height > 8 ~ "> 8 m",
is.na(canopy_height) ~ NA_character_),
levels = c(
"0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
aes(x = EUNISa_1_descr, fill = CH_cat)) +
geom_bar() + theme_bw() + coord_flip() +
scale_y_continuous(labels = label_number()) +
scale_fill_viridis_d(direction = -1) +
labs(x = "EUNIS level 1", fill = "Canopy height") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
theme(legend.position = c(0.8, 0.75),
legend.direction = "vertical")

Stats per habitat type
data_validation %>%
# Keep only forests, grasslands, shrublands and wetlands
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
group_by(EUNISa_1_descr) %>%
summarise(across(canopy_height, list(
mean = mean,
median = median,
sd = sd,
min = min,
max = max
), na.rm = TRUE))
Phenology
Only using NDVI- and SAVI-based values so far.
Maximum NDVI should be equal to value at peak?
nrow(data_validation %>% dplyr::filter(NDVI_pos_value != NDVI_max))
[1] 403
Not sure why this happens, but in most cases the difference is small
(< -0.1). Anyway, we should use only one of these two in the RF
models.
plot_histogram(data_validation, "NDVI_sos_doy", "NDVI_sos_doy")

plot_histogram(data_validation, "NDVI_pos_doy", "NDVI_pos_doy")

plot_histogram(data_validation, "NDVI_eos_doy", "NDVI_eos_doy")

plot_histogram(data_validation, "EVI_sos_doy", "EVI_sos_doy")

plot_histogram(data_validation, "EVI_pos_doy", "EVI_pos_doy")

plot_histogram(data_validation, "EVI_eos_doy", "EVI_eos_doy")

plot_histogram(data_validation, "SAVI_sos_doy", "SAVI_sos_doy")

plot_histogram(data_validation, "SAVI_pos_doy", "SAVI_pos_doy")

plot_histogram(data_validation, "SAVI_eos_doy", "SAVI_eos_doy")

plot_histogram(data_validation, "NDVI_sos_value", "NDVI_sos_value")

plot_histogram(data_validation, "NDVI_pos_value", "NDVI_pos_value")

plot_histogram(data_validation, "NDVI_eos_value", "NDVI_eos_value")

plot_histogram(data_validation, "EVI_sos_value", "EVI_sos_value")

plot_histogram(data_validation, "EVI_pos_value", "EVI_pos_value")

plot_histogram(data_validation, "EVI_eos_value", "EVI_eos_value")

plot_histogram(data_validation, "NDVI_gsd", "NDVI_gsd")

plot_histogram(data_validation, "EVI_gsd", "EVI_gsd")

plot_histogram(data_validation, "SAVI_gsd", "SAVI_gsd")

plot_histogram(data_validation, "NDVI_diff_pos_sos_value",
"NDVI_diff_pos_sos_value")

plot_histogram(data_validation, "EVI_diff_pos_sos_value",
"EVI_diff_pos_sos_value")

plot_histogram(data_validation, "SAVI_diff_pos_sos_value",
"SAVI_diff_pos_sos_value")

plot_histogram(data_validation, "NDVI_diff_pos_eos_value",
"NDVI_diff_pos_eos_value")

plot_histogram(data_validation, "EVI_diff_pos_eos_value",
"EVI_diff_pos_eos_value")

plot_histogram(data_validation, "SAVI_diff_pos_eos_value",
"SAVI_diff_pos_eos_value")

plot_histogram(data_validation, "NDVI_diff_pos_sos_doy",
"NDVI_diff_pos_sos_doy")

plot_histogram(data_validation, "EVI_diff_pos_sos_doy",
"EVI_diff_pos_sos_doy")

plot_histogram(data_validation, "SAVI_diff_pos_sos_doy",
"SAVI_diff_pos_sos_doy")

plot_histogram(data_validation, "NDVI_diff_eos_pos_doy",
"NDVI_diff_eos_pos_doy")

plot_histogram(data_validation, "EVI_diff_eos_pos_doy",
"EVI_diff_eos_pos_doy")

plot_histogram(data_validation, "SAVI_diff_eos_pos_doy",
"SAVI_diff_eos_pos_doy")

plot_histogram(data_validation, "NDVI_auc", "NDVI_auc")

plot_histogram(data_validation, "EVI_auc", "EVI_auc")

plot_histogram(data_validation, "SAVI_auc", "SAVI_auc")

distr_plot(data_validation,
c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
"EVI_sos_value","EVI_pos_value", "EVI_eos_value",
"SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
"NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
"EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
"SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy"),
c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
"EVI_sos_value","EVI_pos_value", "EVI_eos_value",
"SAVI_sos_Value", "SAVI_pos_value", "SAVI_eos_value",
"NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
"EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
"SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy")
)


















distr_plot(data_validation,
c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
"NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
"SAVI_diff_pos_sos_value",
"NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
"SAVI_diff_pos_eos_value",
"NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
"SAVI_diff_pos_sos_doy",
"NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
"SAVI_diff_eos_pos_doy"),
c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
"NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
"SAVI_diff_pos_sos_Value",
"NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
"SAVI_diff_pos_eos_value",
"NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
"SAVI_diff_pos_sos_doy",
"NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
"SAVI_diff_eos_pos_doy")
)















distr_plot(data_validation,
c("NDVI_auc", "EVI_auc", "SAVI_auc"),
c("NDVI_auc", "EVI_auc", "SAVI_auc"))



RF models
vars_RF <- c(
# Min values of all indices
"NDVI_min", "EVI_min", "NDMI_min", "NDWI_min", "SAVI_min",
# Max values of NDMI and NDWI
"NDMI_max", "NDWI_max",
# AUC of NDVI, EVI and SAVI
"NDVI_auc", "EVI_auc", "SAVI_auc",
# Values of NDVI, EVI and SAVI at sos, pos and eos
"NDVI_sos_value", "NDVI_pos_value", "NDVI_eos_value",
"EVI_sos_value", "EVI_pos_value", "EVI_eos_value",
"SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
# Differences pos-sos in value and doy
"NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value", "SAVI_diff_pos_sos_value",
"NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy", "SAVI_diff_pos_sos_doy",
# Differences pos-eos in value and doy
"NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value","SAVI_diff_pos_eos_value",
"NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy", "SAVI_diff_eos_pos_doy",
# Growing season duration
"NDVI_gsd", "EVI_gsd", "SAVI_gsd",
# Canopy height
"canopy_height")
RF with all points
filtered_data0 <- data_validation %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
# Remove all rows with wrong values of indices (not between -1 and 1)
dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
dplyr::filter(NDVI_max <= 1) %>%
dplyr::filter(NDMI_max <= 1) %>%
dplyr::filter(NDWI_min >= -1) %>%
# Remove rows with missing values
dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
# Keep only rows with differences > 0
dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
# Select only variables needed
select(EUNISa_1, all_of(vars_RF))
Correlation of all variables to be included in RF models:
corrplot(filtered_data0 %>%
select(all_of(vars_RF)) %>%
cor(use = "pairwise.complete.obs"),
method = "color", type = "upper", tl.col = "black", tl.srt = 45)

Split into training and test data sets.
train_indices0 <- sample(1:nrow(filtered_data0), 0.7 * nrow(filtered_data0))
train_data0 <- filtered_data0[train_indices0, ]
test_data0 <- filtered_data0[-train_indices0, ]
Number of points per category for filtered data:
filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(
formula = reformulate(vars_RF, response = "EUNISa_1"),
data = train_data0,
controls = cforest_control(mtry = round(sqrt(length(all_of(vars_RF)))),
# mtry = sqrt(35) # sqrt of total n variables
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
OOB = TRUE, type = "response")
beep()
Confusion matrix:
confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
Confusion Matrix and Statistics
Reference
Prediction Q R S T
Q 113 17 10 1
R 378 2114 320 112
S 77 95 570 27
T 26 64 33 608
Overall Statistics
Accuracy : 0.7459
95% CI : (0.733, 0.7585)
No Information Rate : 0.5016
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.5861
Mcnemar's Test P-Value : < 2.2e-16
Statistics by Class:
Class: Q Class: R Class: S Class: T
Sensitivity 0.19024 0.9231 0.6109 0.8128
Specificity 0.99295 0.6440 0.9452 0.9678
Pos Pred Value 0.80142 0.7230 0.7412 0.8317
Neg Pred Value 0.89127 0.8927 0.9044 0.9635
Prevalence 0.13012 0.5016 0.2044 0.1639
Detection Rate 0.02475 0.4631 0.1249 0.1332
Detection Prevalence 0.03089 0.6405 0.1685 0.1601
Balanced Accuracy 0.59159 0.7836 0.7781 0.8903
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F)
Variable Importance Plot
varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc0

RF with all GPS points (diff or not)
filtered_data1 <- data_validation %>%
# Select only GPS points
dplyr::filter(`Location method` == "Location with GPS" |
`Location method` == "Location with differential GPS") %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
# Remove all rows with wrong values of indices (not between -1 and 1)
dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
dplyr::filter(NDVI_max <= 1) %>%
dplyr::filter(NDMI_max <= 1) %>%
dplyr::filter(NDWI_min >= -1) %>%
# Remove rows with missing values
dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
# Keep only rows with differences > 0
dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
# Select only variables needed
select(EUNISa_1, all_of(vars_RF))
Split into training and test data sets.
train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]
Number of points per category for filtered data:
filtered_data1 %>% count(EUNISa_1)
rf_cforest1 <- party::cforest(
formula = reformulate(vars_RF, response = "EUNISa_1"),
data = train_data1,
controls = cforest_control(mtry = round(sqrt(length(all_of(vars_RF)))),
# mtry = sqrt(35) # sqrt of total n variables
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
beep()
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
Confusion Matrix and Statistics
Reference
Prediction Q R S T
Q 158 21 29 0
R 238 1106 156 66
S 96 107 522 13
T 12 27 10 110
Overall Statistics
Accuracy : 0.7098
95% CI : (0.6922, 0.727)
No Information Rate : 0.4721
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.5395
Mcnemar's Test P-Value : < 2.2e-16
Statistics by Class:
Class: Q Class: R Class: S Class: T
Sensitivity 0.31349 0.8771 0.7280 0.58201
Specificity 0.97693 0.6738 0.8895 0.98026
Pos Pred Value 0.75962 0.7063 0.7073 0.69182
Neg Pred Value 0.85952 0.8597 0.8991 0.96855
Prevalence 0.18869 0.4721 0.2684 0.07076
Detection Rate 0.05915 0.4141 0.1954 0.04118
Detection Prevalence 0.07787 0.5863 0.2763 0.05953
Balanced Accuracy 0.64521 0.7754 0.8087 0.78113
varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F)
Variable Importance Plot
varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc1

RF with …
First validation
For T, R, S, Q habitats.
Define a set of rules for a first validation of ALL ReSurvey data. We
can call these “Expert-based” rules.
Number of observations in ReSurvey from the habitats of interest:
nrow(data_validation %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")))
Number of observations in ReSurvey from the habitats of interest and
with all RS data:
nrow(data_validation %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
dplyr::filter(CH_data == T) %>%
dplyr::filter(RS_data ==T) %>%
dplyr::filter(RS_phen_data == T))
data_validation_terrestrial <- data_validation %>%
dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q"))
Define rules
Create column for first validation based on different indicators,
where “wrong” is noted when the validation rule is not met. Include
EUNIS1 confusions.
data_validation_terrestrial %>% count(EUNISa_1, EUNIS1_conf_type)
Define rules:
data_validation_terrestrial <-
data_validation_terrestrial %>%
mutate(
valid_1_NDWI = case_when(
# Points that are basically water
NDWI_max > 0.3 ~ "wrong",
TRUE ~ NA_character_),
valid_1_CH = case_when(
# T points with low CH
EUNISa_1 == "T" & canopy_height < 8 ~ "wrong",
# S points with low CH
EUNISa_1 =="S" & canopy_height < 5 ~ "wrong",
# R & Q points with high CH
EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
TRUE ~ NA_character_),
valid_1_NDVI = case_when(
# T points with low NDVI_max
EUNISa_1 == "T" & NDVI_max < 0.6 ~ "wrong",
# S-R-Q points with low NDVI_max
EUNISa_1 %in% c("R", "S", "Q") & NDVI_max < 0.2 ~ "wrong",
TRUE ~ NA_character_),
# Count how many validation rules are not met
valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH, valid_1_NDVI),
~ . == "wrong"), na.rm = TRUE),
# Points where at least 1 rule not met
valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
"No rules broken so far")
)
Plots first validation
ggplot(data_validation_terrestrial%>%
mutate(rules_broken = case_when(
valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
valid_1_count == 2 &
valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
valid_1_count == 3 ~ "NDWI + NDVI + CH",
TRUE ~ NA_character_
)),
aes(x = valid_1_count, fill = rules_broken)) +
geom_bar() + labs(x = "Number of broken rules")
data_validation_terrestrial %>%
mutate(rules_broken = case_when(
valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
valid_1_count == 2 &
valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
valid_1_count == 3 ~ "NDWI + NDVI + CH",
TRUE ~ NA_character_
)) %>%
count(rules_broken, EUNIS1_conf_type)
Proportion of observations not validated (so far):
nrow(data_validation_terrestrial %>% dplyr::filter(valid_1_count > 0))/
nrow(data_validation_terrestrial)
But be aware that there are still MANY missing RS data.
ggplot(data_validation_terrestrial %>%
mutate(diff_GPS = if_else(
`Location method` != "Location with differential GPS" |
is.na(`Location method`), "no", "yes")),
aes(x = diff_GPS, fill = valid_1)) +
geom_bar() + labs(x = "Differential GPS")
ggplot(data_validation_terrestrial %>%
mutate(GPS = case_when(
`Location method` == "Location with differential GPS" ~ "yes",
`Location method` == "Location with GPS" ~ "yes",
is.na(`Location method`) ~ "no",
TRUE ~ "no"
)),
aes(x = GPS, fill = valid_1)) +
geom_bar() + labs(x = "GPS")
Points with any rule broken and confusion between EUNIS:
nrow(data_validation_terrestrial %>%
dplyr::filter(EUNIS1_conf == T & valid_1_count > 0))
Convert to shp to look at these in GIS:
# st_write(data_validation_terrestrial %>%
# dplyr::filter(EUNIS1_conf == T & valid_1_count > 0) %>%
# st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
# "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_EUNIS_conf.shp")
Checked and yes
How many points with differential GPS that have at least 1 rule
broken?
nrow(data_validation_terrestrial %>%
dplyr::filter(`Location method` == "Location with differential GPS" &
valid_1 == "At least 1 rule broken"))
Convert to shp to look at these in GIS:
# st_write(data_validation_terrestrial %>%
# dplyr::filter(`Location method` == "Location with differential GPS" &
# valid_1 == "At least 1 rule broken") %>%
# st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
# "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_diff_GPS.shp")
RF models
Using the conditional inference version of random forest (cforest in
package party). Suggested if the data are highly correlated. Cforest is
more stable in deriving variable importance values in the presence of
highly correlated variables, thus providing better accuracy in
calculating variable importance (ref below).
Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive
Portioning: A Conditional Inference Framework. Journal of Computational
and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933
All GPS points
dplyr::filtered_data0 <- all_GPS_valid %>%
dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
dplyr::filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices0 <- sample(1:nrow(dplyr::filtered_data0), 0.7 * nrow(dplyr::filtered_data0))
train_data0 <- dplyr::filtered_data0[train_indices0, ]
test_data0 <- dplyr::filtered_data0[-train_indices0, ]
Number of points per category for dplyr::filtered data:
dplyr::filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data0,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F)
Variable Importance Plot
varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc0
REVISE FROM HERE: All GPS points above p20
dplyr::filter the data to get only GPS-points above p20 of NDVI_max
and NDMI_min.
all_GPS_valid <- all_GPS_valid %>%
select(-percentile_20_NDVI_max, -percentile_20_NDMI_min)
percentiles <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
percentile_20_NDVI_max = quantile(NDVI_max, 0.20, na.rm = T),
percentile_20_NDMI_min = quantile(NDMI_min, 0.20, na.rm = T),
percentile_80_NDVI_max = quantile(NDVI_max, 0.80, na.rm = T),
percentile_80_NDMI_min = quantile(NDMI_min, 0.80, na.rm = T)
)
# Join the percentiles back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(percentiles, by = "EUNISa_1")
# dplyr::filter rows above the 20th percentile for both variables for each category of EUNISa_1
dplyr::filtered_data1 <- all_GPS_valid %>%
dplyr::filter(
NDVI_max >= percentile_20_NDVI_max & NDMI_min >= percentile_20_NDMI_min
) %>%
dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
dplyr::filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices1 <- sample(1:nrow(dplyr::filtered_data1), 0.7 * nrow(dplyr::filtered_data1))
train_data1 <- dplyr::filtered_data1[train_indices1, ]
test_data1 <- dplyr::filtered_data1[-train_indices1, ]
Number of points per category for dplyr::filtered data:
dplyr::filtered_data1 %>% count(EUNISa_1)
Investigate package ggparty (e.g. autoplot function, and more).
TO-DO: Choose the hyperparameter mtry based on the square root of the
number of predictor variables (Hastie et al., 2009)-
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements
of statistical learning: Data mining, inference, and prediction.
Springer Science & Business Media.
Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving
mtry constant at 2. Tis parameter variation showed that ntree=500 was
optimal, while higher ntree led to no further model improvement
(Supplementary Fig. S10). Subsequently, the hyperparameter mtry was
varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best
results in almost all cases (Supplementary Fig. S11). Consequently, we
chose ntree=500 and mtry=3 for our main analysis across all study
sites.
rf_cforest1 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data1,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
SurrogateTree –> does not work
varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F)
Variable Importance Plot
varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
Tree Visualization
# Create a single conditional inference tree using ctree
single_tree1 <- ctree(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max + NDMI_min +
NDWI_max + NDWI_min + EVI_max + EVI_min + SAVI_max +
SAVI_min + canopy_height,
data = train_data1)
# Plot the single tree using
autoplot(single_tree1)
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc1
All GPS points within IQ range
dplyr::filter the data to get only GPS-points within IQ range of
NDVI_max and NDMI_min.
IQ_ranges <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
Q1_NDVI_max = quantile(NDVI_max, 0.25, na.rm = T),
Q1_NDMI_min = quantile(NDMI_min, 0.25, na.rm = T),
Q3_NDVI_max = quantile(NDVI_max, 0.75, na.rm = T),
Q3_NDMI_min = quantile(NDMI_min, 0.75, na.rm = T),
IQR_NDVI_max = IQR(NDVI_max, na.rm = TRUE),
IQR_NDMI_min = IQR(NDMI_min, na.rm = TRUE)
)
# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(IQ_ranges, by = "EUNISa_1")
# Filter rows within the IQR range for both variables
dplyr::filtered_data2 <- all_GPS_valid %>%
dplyr::filter(
(NDVI_max >= Q1_NDVI_max & NDVI_max <= Q3_NDVI_max) &
(NDMI_min >= Q1_NDMI_min & NDMI_min <= Q3_NDMI_min)
) %>%
dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
dplyr::filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]
Number of points per category for filtered data:
filtered_data2 %>% count(EUNISa_1)
rf_cforest2 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data2,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest2 <- predict(rf_cforest2, newdata = test_data2,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest2, test_data2$EUNISa_1)
varimp_rf_cforest2 <- party::varimp(rf_cforest2, conditional = F)
Variable Importance Plot
varimp_rf_cforest2_df <- data.frame(Variable = names(varimp_rf_cforest2),
Importance = varimp_rf_cforest2)
ggplot(varimp_rf_cforest2_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc2 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc2
All GPS points within 1.5 * IQ range
Filter the data to get only GPS-points within 1.5 * IQ range of
NDVI_max and NDMI_min.
# Filter rows within the 1.5 * IQR range for both variables
filtered_data3 <- all_GPS_valid %>%
filter(
(NDVI_max >= (Q1_NDVI_max - 1.5 * IQR_NDVI_max) & NDVI_max <= (Q3_NDVI_max + 1.5 * IQR_NDVI_max)) &
(NDMI_min >= (Q1_NDMI_min - 1.5 * IQR_NDMI_min) & NDMI_min <= (Q3_NDMI_min + 1.5 * IQR_NDMI_min))
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]
Number of points per category for filtered data:
filtered_data3 %>% count(EUNISa_1)
rf_cforest3 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data3,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest3 <- predict(rf_cforest3, newdata = test_data3,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest3, test_data3$EUNISa_1)
varimp_rf_cforest3 <- party::varimp(rf_cforest3, conditional = F)
Variable Importance Plot
varimp_rf_cforest3_df <- data.frame(Variable = names(varimp_rf_cforest3),
Importance = varimp_rf_cforest3)
ggplot(varimp_rf_cforest3_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc3 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc3
All GPS points within mean +/- SD
Filter the data to get only GPS-points within mean +/- SD of NDVI_max
and NDMI_min.
mean_sd <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
mean_NDVI_max = mean(all_GPS_valid$NDVI_max, na.rm = T),
mean_NDMI_min = mean(all_GPS_valid$NDMI_min, na.rm = T),
sd_NDVI_max = sd(all_GPS_valid$NDVI_max, na.rm = T),
sd_NDMI_min = sd(all_GPS_valid$NDMI_min, na.rm = T)
)
# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(mean_sd, by = "EUNISa_1")
# Filter rows within the specified range for both variables
filtered_data4 <- all_GPS_valid %>%
filter(
(NDVI_max >= (mean_NDVI_max - sd_NDVI_max) & NDVI_max <= (mean_NDVI_max + sd_NDVI_max)) &
(NDMI_min >= (mean_NDMI_min - sd_NDMI_min) & NDMI_min <= (mean_NDMI_min + sd_NDMI_min))
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]
Number of points per category for filtered data:
filtered_data4 %>% count(EUNISa_1)
rf_cforest4 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data4,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest4 <- predict(rf_cforest4, newdata = test_data4,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest4, test_data4$EUNISa_1)
varimp_rf_cforest4 <- party::varimp(rf_cforest4, conditional = F)
Variable Importance Plot
varimp_rf_cforest4_df <- data.frame(Variable = names(varimp_rf_cforest4),
Importance = varimp_rf_cforest4)
ggplot(varimp_rf_cforest4_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc4 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc4
All GPS points above p20 and below p80
Filter the data to get only GPS-points above p20 and below p80 of
NDVI_max and NDMI_min.
# Filter rows above the 20th percentile and below the 80th percentile for both variables
filtered_data5 <- all_GPS_valid %>%
filter(
(NDVI_max >= percentile_20_NDVI_max & NDVI_max <= percentile_80_NDVI_max) &
(NDMI_min >= percentile_20_NDMI_min & NDMI_min <= percentile_80_NDMI_min)
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices5 <- sample(1:nrow(filtered_data5), 0.7 * nrow(filtered_data5))
train_data5 <- filtered_data5[train_indices5, ]
test_data5 <- filtered_data5[-train_indices5, ]
Number of points per category for filtered data:
filtered_data5 %>% count(EUNISa_1)
rf_cforest5 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data5,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest5 <- predict(rf_cforest5, newdata = test_data5,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest5, test_data5$EUNISa_1)
varimp_rf_cforest5 <- party::varimp(rf_cforest5, conditional = F)
Variable Importance Plot
varimp_rf_cforest5_df <- data.frame(Variable = names(varimp_rf_cforest5),
Importance = varimp_rf_cforest5)
ggplot(varimp_rf_cforest5_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc5 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc5
Cordillera data
AlpineGrasslands_indices <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
AlpineGrasslands_phen <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
AlpineGrasslands_CH <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
VegetationTypes_indices <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
VegetationTypes_phen <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
VegetationTypes_CH <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
AlpineGrasslands <- AlpineGrasslands_indices %>%
select(-`system:index`, -.geo, -Localidad) %>%
rename(Hábitat = "H�bitat") %>%
full_join(AlpineGrasslands_phen %>%
select(-`system:index`, -.geo, -Localidad) %>%
rename(Hábitat = "H�bitat")) %>%
full_join(AlpineGrasslands_CH %>%
select(-`system:index`, -.geo, -Localidad)) %>%
select(-Date__year, - `Precisi�n`) %>%
mutate(DATE = ymd(DATE)) %>%
rename(ID = "Releve_num") %>%
mutate(ID = as.character(ID)) %>%
mutate(layer = "AlpineGrasslands")
VegetationTypes <- VegetationTypes_indices %>%
select(-`system:index`, -.geo) %>%
full_join(VegetationTypes_phen %>%
select(-`system:index`, -.geo)) %>%
full_join(VegetationTypes_CH %>%
select(-`system:index`, -.geo)) %>%
rename(Hábitat = "TYPE") %>%
mutate(layer = "VegetationTypes")
Merge both datasets:
cordillera <- bind_rows(
AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
starts_with("NDVI"), Hábitat, "EOS_DOY",
"Peak_DOY", "SOS_DOY", "Season_Length",
"canopy_height", "layer"),
VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
starts_with("NDVI"), Hábitat, "EOS_DOY",
"Peak_DOY", "SOS_DOY", "Season_Length",
"canopy_height", "layer")
) %>%
mutate(EUNISa_1 = case_when(
Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
Hábitat = str_detect(Hábitat, "forest") ~ "T",
Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
Hábitat = is.na(Hábitat) ~ "R",
TRUE ~ NA_character_),
EUNISa_1_descr = case_when(
EUNISa_1 == "R" ~ "Grasslands",
EUNISa_1 == "T" ~ "Forests and other wooded land",
EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
)
NDVI, NDMI
distr_plot(cordillera,
c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"),
c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot(cordillera,
c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"),
c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSINCnN1YnRpdGxlOiAiVmFsaWRhdGlvbiBkb25lIHdpdGggYSBzYW1wbGUgb2YgcG9pbnRzIChsYXN0IG9ic2VydmF0aW9ucykiDQphdXRob3I6ICJBbGljaWEgVmFsZMOpcyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSkNCmBgYA0KDQpUaGlzIFIgc2NyaXB0IGlzIHVzZWQgdG8gdmFsaWRhdGUgdGhlIHBvaW50cyBpbiB0aGUgUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgaW5kaWNhdG9ycyAoaW5kaWNlcyArIHBoZW5vbG9neSArIGNhbm9weSBoZWlnaHQpLg0KDQojIExvYWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShkdHBseXIpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGxtZXJUZXN0KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkocGFydHkpDQpsaWJyYXJ5KHBhcnR5a2l0KQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3JycGxvdCkNCmBgYA0KDQojIERlZmluZSBwcmludGFsbCBmdW5jdGlvbg0KDQpgYGB7cn0NCnByaW50YWxsIDwtIGZ1bmN0aW9uKHRpYmJsZSkgew0KICBwcmludCh0aWJibGUsIHdpZHRoID0gSW5mKQ0KICB9DQpgYGANCg0KIyBMb2FkIGdlb21fZmxhdF92aW9saW4gcGxvdA0KDQpgYGB7cn0NCnNvdXJjZSgiaHR0cHM6Ly9naXN0LmdpdGh1YnVzZXJjb250ZW50LmNvbS9iZW5tYXJ3aWNrLzJhMWJiMDEzM2ZmNTY4Y2JlMjhkL3Jhdy9mYjUzYmQ5NzEyMWY3ZjljZTk0NzgzN2VmMWE0YzY1YTczYmZmYjNmL2dlb21fZmxhdF92aW9saW4uUiIpDQpgYGANCg0KIyBSZWFkIGRhdGENCg0KYGBge3J9DQpkYXRhX3ZhbGlkYXRpb248LXJlYWRfdHN2KGhlcmUoImRhdGEiLCAiY2xlYW4iLCJmaW5hbF9SU19kYXRhX2JhbmRzX1MyX3NhbXBsZS5jc3YiKSkNCmBgYA0KDQpObyBwYXJzaW5nIGlzc3VlcyENCg0KIyBTb21lIGRhdGEgbWFuYWdlbmVtdA0KDQojIyBUTy1ETzogTWlzc2luZyBkYXRhIGNoZWNrcw0KDQpEbyB3aGVuIGFsbCBSUyBkYXRhIGlzIHJlYWR5IQ0KDQojIERpc3RyaWJ1dGlvbnMgYWxsIGJpb3JlZ2lvbnMNCg0KIyMgSW5kaWNlcw0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIGhpc3RvZ3JhbXMNCnBsb3RfaGlzdG9ncmFtIDwtIGZ1bmN0aW9uKGRhdGEsIHhfdmFyLCB4X2xhYmVsKSB7DQogIGdncGxvdChkYXRhICU+JQ0KICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgIGFlcyh4ID0gISFzeW0oeF92YXIpKSkgKw0KICAgIGdlb21faGlzdG9ncmFtKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgICBsYWJzKHggPSB4X2xhYmVsLCB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgICB0aGVtZV9idygpDQp9DQpgYGANCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNyZWF0ZSBwbG90cyB3aXRoIHZpb2xpbiArIGJveHBsb3QgKyBwb2ludHMNCmRpc3RyX3Bsb3QgPC0gZnVuY3Rpb24oZGF0YSwgeV92YXJzLCB5X2xhYmVscykgew0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHlfdmFycykpIHsNCiAgICB5X3ZhciA8LSB5X3ZhcnNbW2ldXQ0KICAgIHlfbGFiZWwgPC0geV9sYWJlbHNbW2ldXQ0KICAgIA0KICAgIHAgPC0gZ2dwbG90KGRhdGEgPSBkYXRhICU+JQ0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICAgICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9ICEhc3ltKHlfdmFyKSwgZmlsbCA9IEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdlb21fcG9pbnQoYWVzKHkgPSAhIXN5bSh5X3ZhciksIGNvbG9yID0gRVVOSVNhXzFfZGVzY3IpLA0KICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogICAgICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgICAgIGxhYnMoeSA9IHlfbGFiZWwsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgICAgIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCiAgICANCiAgICBwcmludChwKQ0KICB9DQp9DQpgYGANCg0KUmFuZ2VzIG9mIG1pbiBhbmQgbWF4Og0KDQpgYGB7cn0NCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORFZJX21heCwgbmEucm0gPSBUKSAjIE5EVklfbWF4ID4gMSAoc2xpZ2h0bHkpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRNSV9tYXgsIG5hLnJtID0gVCkgIyBORE1JX21heCA+IDEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EV0lfbWF4LCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kU0FWSV9tYXgsIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRFVklfbWF4LCBuYS5ybSA9IFQpICMgRVZJX21heCA+IDEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EVklfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRNSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORFdJX21pbiwgbmEucm0gPSBUKSAjIE5EV0lfbWluIDwgLTEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJFNBVklfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kRVZJX21pbiwgbmEucm0gPSBUKSAjIEVWSV9taW4gPCAtMSENCmBgYA0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4ID4gMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihORE1JX21heCA+IDEpKQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEpKQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPCAtMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihFVklfbWluIDwgLTEpKQ0KYGBgDQoNCkhpc3RvZ3JhbXMgdG8gY2hlY2sgdGhhdCBtYXggYW5kIG1pbiB2YWx1ZXMgYXJlIG9rOg0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfbWF4IiwgIk5EVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5ETUlfbWF4IiwgIk5ETUkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EV0lfbWF4IiwgIk5EV0kgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfbWF4IiwgIlNBVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9tYXgiLCAiRVZJIG1heCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX21pbiIsICJORFZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORE1JX21pbiIsICJORE1JIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFdJX21pbiIsICJORFdJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX21pbiIsICJTQVZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24gJT4lDQogICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+IC0xLjUpLCAiRVZJX21pbiIsICJFVkkgbWluIikNCmBgYA0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEgfCBFVklfbWF4IDwgLTEpKQ0KZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEgfCBFVklfbWF4IDwgLTEpICU+JQ0KICBjb3VudChiaW9nZW8sIHVuaXQpDQpgYGANCg0KTW9zdCBFVkkgdmFsdWVzIGFyZSBvayENCg0KRGlzdHJpYnV0aW9uIHBsb3RzOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+IC0wLjUpLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJFVklfbWF4IiwgIlNBVklfbWF4IiwgIk5ETUlfbWF4IiwgIk5EV0lfbWF4IiwNCiAgICAgICAgICAgICAiTkRWSV9taW4iLCAiRVZJX21pbiIsICJTQVZJX21pbiIsICJORE1JX21pbiIsICJORFdJX21pbiIpLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJFVklfbWF4IiwgIlNBVklfbWF4IiwgIk5ETUlfbWF4IiwgIk5EV0lfbWF4IiwNCiAgICAgICAgICAgICAiTkRWSV9taW4iLCAiRVZJX21pbiIsICJTQVZJX21pbiIsICJORE1JX21pbiIsICJORFdJX21pbiIpKQ0KYGBgDQoNCiMjIENIDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sICJjYW5vcHlfaGVpZ2h0IiwgIkNhbm9weSBoZWlnaHQgKG0pIikNCmBgYA0KIA0KIyMjIFNob3cgaGFiaXRhdHMgd2l0aCBDSCBjYXRlZ29yaWVzDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShDSF9jYXQgPQ0KICAgICAgICAgICAgICAgICAgZmFjdG9yKA0KICAgICAgICAgICAgICAgICAgICBjYXNlX3doZW4oY2Fub3B5X2hlaWdodCA9PSAwIH4gIjAgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMCAmIGNhbm9weV9oZWlnaHQgPD0gMSB+ICIwLTEgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMSAmIGNhbm9weV9oZWlnaHQgPD0yIH4gIjEtMiBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiAyICYgY2Fub3B5X2hlaWdodCA8PTUgfiAiMi01IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDUgJiBjYW5vcHlfaGVpZ2h0IDw9OCB+ICI1LTggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gOCB+ICI+IDggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShjYW5vcHlfaGVpZ2h0KSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKA0KICAgICAgICAgICAgICAgICAgICAgICIwIG0iLCAiMC0xIG0iLCAiMS0yIG0iLCAiMi01IG0iLCAiNS04IG0iLCAiPiA4IG0iKSkpLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIGZpbGwgPSBDSF9jYXQpKSArDQogIGdlb21fYmFyKCkgKyB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX251bWJlcigpKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xKSArDQogIGxhYnMoeCA9ICJFVU5JUyBsZXZlbCAxIiwgZmlsbCA9ICJDYW5vcHkgaGVpZ2h0IikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44LCAwLjc1KSwNCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpDQpgYGANCg0KIyMjIFN0YXRzIHBlciBoYWJpdGF0IHR5cGUNCg0KYGBge3J9DQpkYXRhX3ZhbGlkYXRpb24gJT4lDQogICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBncm91cF9ieShFVU5JU2FfMV9kZXNjcikgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoY2Fub3B5X2hlaWdodCwgbGlzdCgNCiAgICBtZWFuID0gbWVhbiwNCiAgICBtZWRpYW4gPSBtZWRpYW4sDQogICAgc2QgPSBzZCwNCiAgICBtaW4gPSBtaW4sDQogICAgbWF4ID0gbWF4DQogICAgKSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KT25seSB1c2luZyBORFZJLSBhbmQgU0FWSS1iYXNlZCB2YWx1ZXMgc28gZmFyLg0KDQpNYXhpbXVtIE5EVkkgc2hvdWxkIGJlIGVxdWFsIHRvIHZhbHVlIGF0IHBlYWs/DQoNCmBgYHtyfQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoTkRWSV9wb3NfdmFsdWUgICE9IE5EVklfbWF4KSkNCmBgYA0KDQpOb3Qgc3VyZSB3aHkgdGhpcyBoYXBwZW5zLCBidXQgaW4gbW9zdCBjYXNlcyB0aGUgZGlmZmVyZW5jZSBpcyBzbWFsbCAoPCAtMC4xKS4gQW55d2F5LCB3ZSBzaG91bGQgdXNlIG9ubHkgb25lIG9mIHRoZXNlIHR3byBpbiB0aGUgUkYgbW9kZWxzLg0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfc29zX2RveSIsICJORFZJX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9wb3NfZG95IiwgIk5EVklfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2Vvc19kb3kiLCAiTkRWSV9lb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9zb3NfZG95IiwgIkVWSV9zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9wb3NfZG95IiwgIkVWSV9wb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9lb3NfZG95IiwgIkVWSV9lb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfc29zX2RveSIsICJTQVZJX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9wb3NfZG95IiwgIlNBVklfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2Vvc19kb3kiLCAiU0FWSV9lb3NfZG95IikNCmBgYA0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfc29zX3ZhbHVlIiwgIk5EVklfc29zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfcG9zX3ZhbHVlIiwgIk5EVklfcG9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZW9zX3ZhbHVlIiwgIk5EVklfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9zb3NfdmFsdWUiLCAiRVZJX3Nvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfcG9zX3ZhbHVlIiwgIkVWSV9wb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2Vvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIikNCmBgYA0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZ3NkIiwgIk5EVklfZ3NkIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9nc2QiLCAiRVZJX2dzZCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2dzZCIsICJTQVZJX2dzZCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9wb3NfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgICAgICAgICAgICAgICJFVklfZGlmZl9wb3Nfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9lb3NfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZGlmZl9lb3NfcG9zX2RveSIsIA0KICAgICAgICAgICAgICAgIkVWSV9kaWZmX2Vvc19wb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIsIA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIpDQpgYGANCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2F1YyIsICJORFZJX2F1YyIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfYXVjIiwgIkVWSV9hdWMiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9hdWMiLCAiU0FWSV9hdWMiKQ0KYGBgDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sDQogICAgICAgICAgIGMoIk5EVklfc29zX3ZhbHVlIiwiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJFVklfc29zX3ZhbHVlIiwiRVZJX3Bvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9zb3NfdmFsdWUiLCAiU0FWSV9wb3NfdmFsdWUiLCAiU0FWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX3Nvc19kb3kiLCJORFZJX3Bvc19kb3kiLCAiTkRWSV9lb3NfZG95IiwNCiAgICAgICAgICAgICAiRVZJX3Nvc19kb3kiLCJFVklfcG9zX2RveSIsICJFVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfc29zX2RveSIsICJTQVZJX3Bvc19kb3kiLCAiU0FWSV9lb3NfZG95IiksDQogICAgICAgICAgIGMoIk5EVklfc29zX3ZhbHVlIiwiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJFVklfc29zX3ZhbHVlIiwiRVZJX3Bvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9zb3NfVmFsdWUiLCAiU0FWSV9wb3NfdmFsdWUiLCAiU0FWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX3Nvc19kb3kiLCJORFZJX3Bvc19kb3kiLCAiTkRWSV9lb3NfZG95IiwNCiAgICAgICAgICAgICAiRVZJX3Nvc19kb3kiLCJFVklfcG9zX2RveSIsICJFVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfc29zX2RveSIsICJTQVZJX3Bvc19kb3kiLCAiU0FWSV9lb3NfZG95IikNCiAgICAgICAgICAgKQ0KYGBgDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sDQogICAgICAgICAgIGMoIk5EVklfZ3NkIiwiRVZJX2dzZCIsICJTQVZJX2dzZCIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX2RveSIsICJFVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9lb3NfcG9zX2RveSIsICJFVklfZGlmZl9lb3NfcG9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIpLA0KICAgICAgICAgICBjKCJORFZJX2dzZCIsIkVWSV9nc2QiLCAiU0FWSV9nc2QiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsICJFVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfVmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc19kb3kiLCAiRVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLCAiRVZJX2RpZmZfZW9zX3Bvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfZW9zX3Bvc19kb3kiKQ0KICAgICAgICAgICApDQpgYGANCg0KYGBge3J9DQpkaXN0cl9wbG90KGRhdGFfdmFsaWRhdGlvbiwNCiAgICAgICAgICAgYygiTkRWSV9hdWMiLCAiRVZJX2F1YyIsICJTQVZJX2F1YyIpLA0KICAgICAgICAgICAgIGMoIk5EVklfYXVjIiwgIkVWSV9hdWMiLCAiU0FWSV9hdWMiKSkNCmBgYA0KDQojIFRCRDogRGlzdHJpYnV0aW9ucyBwZXIgYmlvcmVnaW9uDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgd2l0aCB2aW9saW4gKyBib3hwbG90ICsgcG9pbnRzDQpkaXN0cl9wbG90X2Jpb2dlbyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIHBsb3RzIDwtIGxpc3QoKQ0KICANCiAgZm9yIChpIGluIHNlcV9hbG9uZyh5X3ZhcnMpKSB7DQogICAgeV92YXIgPC0geV92YXJzW1tpXV0NCiAgICB5X2xhYmVsIDwtIHlfbGFiZWxzW1tpXV0NCiAgICANCiAgICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSAlPiUNCiAgICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSAhIXN5bSh5X3ZhciksIGZpbGwgPSBFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogICAgICBnZW9tX3BvaW50KGFlcyh5ID0gISFzeW0oeV92YXIpLCBjb2xvciA9IEVVTklTYV8xX2Rlc2NyKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICAgICAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogICAgICBsYWJzKHkgPSB5X2xhYmVsLCB4ID0gIkVVTklTYV8xX2Rlc2NyIikgKw0KICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgICAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKSArIGZhY2V0X3dyYXAofiBiaW9nZW8pDQogICAgDQogICAgcGxvdHNbW3lfdmFyXV0gPC0gcA0KICB9DQogIA0KICByZXR1cm4ocGxvdHMpDQp9DQpgYGANCg0KIyMgSW5kaWNlcw0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCiMjIENIDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGF0YV92YWxpZGF0aW9uLCAiY2Fub3B5X2hlaWdodCIsICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpgYGANCg0KIyMgUGhlbm9sb2d5DQoNCiMgUkYgbW9kZWxzDQoNCmBgYHtyfQ0KdmFyc19SRiA8LSBjKA0KICAjIE1pbiB2YWx1ZXMgb2YgYWxsIGluZGljZXMNCiAgIk5EVklfbWluIiwgIkVWSV9taW4iLCAiTkRNSV9taW4iLCAiTkRXSV9taW4iLCAiU0FWSV9taW4iLA0KICAjIE1heCB2YWx1ZXMgb2YgTkRNSSBhbmQgTkRXSQ0KICAiTkRNSV9tYXgiLCAiTkRXSV9tYXgiLA0KICAjIEFVQyBvZiBORFZJLCBFVkkgYW5kIFNBVkkNCiAgIk5EVklfYXVjIiwgIkVWSV9hdWMiLCAiU0FWSV9hdWMiLA0KICAjIFZhbHVlcyBvZiBORFZJLCBFVkkgYW5kIFNBVkkgYXQgc29zLCBwb3MgYW5kIGVvcw0KICAiTkRWSV9zb3NfdmFsdWUiLCAiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAiRVZJX3Nvc192YWx1ZSIsICJFVklfcG9zX3ZhbHVlIiwgIkVWSV9lb3NfdmFsdWUiLCANCiAgIlNBVklfc29zX3ZhbHVlIiwgIlNBVklfcG9zX3ZhbHVlIiwgIlNBVklfZW9zX3ZhbHVlIiwNCiAgIyBEaWZmZXJlbmNlcyBwb3Mtc29zIGluIHZhbHVlIGFuZCBkb3kNCiAgIk5EVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLCAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAiTkRWSV9kaWZmX3Bvc19zb3NfZG95IiwgIkVWSV9kaWZmX3Bvc19zb3NfZG95IiwgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICMgRGlmZmVyZW5jZXMgcG9zLWVvcyBpbiB2YWx1ZSBhbmQgZG95DQogICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAiTkRWSV9kaWZmX2Vvc19wb3NfZG95IiwgIkVWSV9kaWZmX2Vvc19wb3NfZG95IiwgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIsDQogICMgR3Jvd2luZyBzZWFzb24gZHVyYXRpb24NCiAgIk5EVklfZ3NkIiwgIkVWSV9nc2QiLCAiU0FWSV9nc2QiLA0KICAjIENhbm9weSBoZWlnaHQNCiAgImNhbm9weV9oZWlnaHQiKQ0KYGBgDQoNCiMjIFJGIHdpdGggYWxsIHBvaW50cw0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEwIDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogICMgUmVtb3ZlIGFsbCByb3dzIHdpdGggd3JvbmcgdmFsdWVzIG9mIGluZGljZXMgKG5vdCBiZXR3ZWVuIC0xIGFuZCAxKQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EV0lfbWluID49IC0xKSAlPiUNCiAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGFsbF9vZih2YXJzX1JGKSwgfiAhaXMubmEoLikpKSAlPiUNCiAgIyBLZWVwIG9ubHkgcm93cyB3aXRoIGRpZmZlcmVuY2VzID4gMA0KICBkcGx5cjo6ZmlsdGVyKGlmX2FsbChjb250YWlucygiZGlmZiIpLCB+IC54ID4gMCkpICU+JQ0KICAjIFNlbGVjdCBvbmx5IHZhcmlhYmxlcyBuZWVkZWQNCiAgc2VsZWN0KEVVTklTYV8xLCBhbGxfb2YodmFyc19SRikpDQpgYGANCg0KQ29ycmVsYXRpb24gb2YgYWxsIHZhcmlhYmxlcyB0byBiZSBpbmNsdWRlZCBpbiBSRiBtb2RlbHM6DQoNCmBgYHtyfQ0KY29ycnBsb3QoZmlsdGVyZWRfZGF0YTAgJT4lIA0KICAgICAgICAgICBzZWxlY3QoYWxsX29mKHZhcnNfUkYpKSAlPiUNCiAgICAgICAgICAgY29yKHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSwNCiAgICAgICAgIG1ldGhvZCA9ICJjb2xvciIsIHR5cGUgPSAidXBwZXIiLCB0bC5jb2wgPSAiYmxhY2siLCB0bC5zcnQgPSA0NSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEwKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMCkpDQp0cmFpbl9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFstdHJhaW5faW5kaWNlczAsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MCA8LSBwYXJ0eTo6Y2ZvcmVzdCgNCiAgZm9ybXVsYSA9IHJlZm9ybXVsYXRlKHZhcnNfUkYsIHJlc3BvbnNlID0gIkVVTklTYV8xIiksDQogIGRhdGEgPSB0cmFpbl9kYXRhMCwgDQogIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKG10cnkgPSByb3VuZChzcXJ0KGxlbmd0aChhbGxfb2YodmFyc19SRikpKSksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgzNSkgIyBzcXJ0IG9mIHRvdGFsIG4gdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDAsIHRlc3RfZGF0YTAkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDBfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QwKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0MF9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMCRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMCA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzANCmBgYA0KDQojIyBSRiB3aXRoIGFsbCBHUFMgcG9pbnRzIChkaWZmIG9yIG5vdCkNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMSA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogICMgU2VsZWN0IG9ubHkgR1BTIHBvaW50cw0KICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfA0KICAgICAgICAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgIyBSZW1vdmUgYWxsIHJvd3Mgd2l0aCB3cm9uZyB2YWx1ZXMgb2YgaW5kaWNlcyAobm90IGJldHdlZW4gLTEgYW5kIDEpDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRWSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRNSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPj0gLTEpICU+JQ0KICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMNCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoYWxsX29mKHZhcnNfUkYpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgU2VsZWN0IG9ubHkgdmFyaWFibGVzIG5lZWRlZA0KICBzZWxlY3QoRVVOSVNhXzEsIGFsbF9vZih2YXJzX1JGKSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMSA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGExKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMSkpDQp0cmFpbl9kYXRhMSA8LSBmaWx0ZXJlZF9kYXRhMVt0cmFpbl9pbmRpY2VzMSwgXQ0KdGVzdF9kYXRhMSA8LSBmaWx0ZXJlZF9kYXRhMVstdHJhaW5faW5kaWNlczEsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMSAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdCgNCiAgZm9ybXVsYSA9IHJlZm9ybXVsYXRlKHZhcnNfUkYsIHJlc3BvbnNlID0gIkVVTklTYV8xIiksDQogIGRhdGEgPSB0cmFpbl9kYXRhMSwgDQogIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKG10cnkgPSByb3VuZChzcXJ0KGxlbmd0aChhbGxfb2YodmFyc19SRikpKSksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgzNSkgIyBzcXJ0IG9mIHRvdGFsIG4gdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDEsIHRlc3RfZGF0YTEkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDEgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MSwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MSwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MSwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDFfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QxKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0MV9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQojIyBSRiB3aXRoIC4uLg0KDQoNCiMgT0xEIEZST00gSEVSRQ0KDQojIEZpcnN0IHZhbGlkYXRpb24NCg0KRm9yIFQsIFIsIFMsIFEgaGFiaXRhdHMuDQoNCkRlZmluZSBhIHNldCBvZiBydWxlcyBmb3IgYSBmaXJzdCB2YWxpZGF0aW9uIG9mIEFMTCBSZVN1cnZleSBkYXRhLiBXZSBjYW4gY2FsbCB0aGVzZSAiRXhwZXJ0LWJhc2VkIiBydWxlcy4NCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdDoNCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSkNCmBgYA0KDQpOdW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIFJlU3VydmV5IGZyb20gdGhlIGhhYml0YXRzIG9mIGludGVyZXN0IGFuZCB3aXRoIGFsbCBSUyBkYXRhOg0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoQ0hfZGF0YSA9PSBUKSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFJTX2RhdGEgPT1UKSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFJTX3BoZW5fZGF0YSA9PSBUKSkNCmBgYA0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpDQpgYGANCg0KIyMgRGVmaW5lIHJ1bGVzDQoNCkNyZWF0ZSBjb2x1bW4gZm9yIGZpcnN0IHZhbGlkYXRpb24gYmFzZWQgb24gZGlmZmVyZW50IGluZGljYXRvcnMsIHdoZXJlICJ3cm9uZyIgaXMgbm90ZWQgd2hlbiB0aGUgdmFsaWRhdGlvbiBydWxlIGlzIG5vdCBtZXQuIEluY2x1ZGUgRVVOSVMxIGNvbmZ1c2lvbnMuDQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JSBjb3VudChFVU5JU2FfMSwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpEZWZpbmUgcnVsZXM6DQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsIDwtDQogIGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgbXV0YXRlKA0KICAgIHZhbGlkXzFfTkRXSSA9IGNhc2Vfd2hlbigNCiAgICAgICMgUG9pbnRzIHRoYXQgYXJlIGJhc2ljYWxseSB3YXRlcg0KICAgICAgTkRXSV9tYXggPiAwLjMgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIHZhbGlkXzFfQ0ggPSBjYXNlX3doZW4oDQogICAgICAjIFQgcG9pbnRzIHdpdGggbG93IENIDQogICAgICBFVU5JU2FfMSA9PSAiVCIgJiBjYW5vcHlfaGVpZ2h0IDwgOCB+ICJ3cm9uZyIsDQogICAgICAjIFMgcG9pbnRzIHdpdGggbG93IENIDQogICAgICBFVU5JU2FfMSA9PSJTIiAmIGNhbm9weV9oZWlnaHQgPCA1IH4gIndyb25nIiwNCiAgICAgICMgUiAmIFEgcG9pbnRzIHdpdGggaGlnaCBDSA0KICAgICAgRVVOSVNhXzEgJWluJSBjKCJSIiwgIlEiKSAmIGNhbm9weV9oZWlnaHQgPiAyIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICB2YWxpZF8xX05EVkkgPSBjYXNlX3doZW4oDQogICAgICAjIFQgcG9pbnRzIHdpdGggbG93IE5EVklfbWF4DQogICAgICBFVU5JU2FfMSA9PSAiVCIgJiBORFZJX21heCA8IDAuNiB+ICJ3cm9uZyIsDQogICAgICAjIFMtUi1RIHBvaW50cyB3aXRoIGxvdyBORFZJX21heA0KICAgICAgRVVOSVNhXzEgJWluJSBjKCJSIiwgIlMiLCAiUSIpICYgTkRWSV9tYXggPCAwLjIgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICMgQ291bnQgaG93IG1hbnkgdmFsaWRhdGlvbiBydWxlcyBhcmUgbm90IG1ldA0KICAgIHZhbGlkXzFfY291bnQgPSByb3dTdW1zKGFjcm9zcyhjKHZhbGlkXzFfTkRXSSwgdmFsaWRfMV9DSCwgdmFsaWRfMV9ORFZJKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gLiA9PSAid3JvbmciKSwgbmEucm0gPSBUUlVFKSwNCiAgICAjIFBvaW50cyB3aGVyZSBhdCBsZWFzdCAxIHJ1bGUgbm90IG1ldA0KICAgIHZhbGlkXzEgPSBpZl9lbHNlKHZhbGlkXzFfY291bnQgPiAwLCAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIsDQogICAgICAgICAgICAgICAgICAgICAgIk5vIHJ1bGVzIGJyb2tlbiBzbyBmYXIiKQ0KICAgICkNCmBgYA0KDQojIyBQbG90cyBmaXJzdCB2YWxpZGF0aW9uDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSksIA0KICAgICAgIGFlcyh4ID0gdmFsaWRfMV9jb3VudCwgZmlsbCA9IHJ1bGVzX2Jyb2tlbikpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJOdW1iZXIgb2YgYnJva2VuIHJ1bGVzIikNCmBgYA0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShydWxlc19icm9rZW4gPSBjYXNlX3doZW4oDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciIH4gIk5EV0kiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX05EVkkgPT0gIndyb25nIiB+ICJORFZJIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9DSCA9PSAid3JvbmciIH4gIkNIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EV0kgPT0gIndyb25nIiAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmcifiAiTkRXSSArIE5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRXSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EVkkgPT0gIndyb25nIiAmIHZhbGlkXzFfQ0ggPT0gIndyb25nIn4gIk5EVkkgKyBDSCIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMyB+ICJORFdJICsgTkRWSSArIENIIiwNCiAgICAgICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICAgICAgICkpICU+JQ0KICBjb3VudChydWxlc19icm9rZW4sIEVVTklTMV9jb25mX3R5cGUpDQpgYGANCg0KUHJvcG9ydGlvbiBvZiBvYnNlcnZhdGlvbnMgbm90IHZhbGlkYXRlZCAoc28gZmFyKToNCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUgZHBseXI6OmZpbHRlcih2YWxpZF8xX2NvdW50ID4gMCkpLw0KICBucm93KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCkNCmBgYA0KDQpCdXQgYmUgYXdhcmUgdGhhdCB0aGVyZSBhcmUgc3RpbGwgTUFOWSBtaXNzaW5nIFJTIGRhdGEuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShkaWZmX0dQUyA9IGlmX2Vsc2UoDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgICE9ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgICBpcy5uYShgTG9jYXRpb24gbWV0aG9kYCksICJubyIsICJ5ZXMiKSksIA0KICAgICAgIGFlcyh4ID0gZGlmZl9HUFMsIGZpbGwgPSB2YWxpZF8xKSkgKw0KICBnZW9tX2JhcigpICsgbGFicyh4ID0gIkRpZmZlcmVudGlhbCBHUFMiKQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShHUFMgPSBjYXNlX3doZW4oDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfiAieWVzIiwNCiAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApIH4gIm5vIiwNCiAgICAgICAgICAgVFJVRSB+ICJubyINCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IEdQUywgZmlsbCA9IHZhbGlkXzEpKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiR1BTIikNCmBgYA0KDQpQb2ludHMgd2l0aCBhbnkgcnVsZSBicm9rZW4gYW5kIGNvbmZ1c2lvbiBiZXR3ZWVuIEVVTklTOg0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVMxX2NvbmYgPT0gVCAmIHZhbGlkXzFfY291bnQgPiAwKSkNCmBgYA0KDQpDb252ZXJ0IHRvIHNocCB0byBsb29rIGF0IHRoZXNlIGluIEdJUzoNCg0KYGBge3J9DQojIHN0X3dyaXRlKGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTMV9jb25mID09IFQgJiB2YWxpZF8xX2NvdW50ID4gMCkgJT4lDQojICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTG9uX3VwZGF0ZWQiLCAiTGF0X3VwZGF0ZWQiKSwgY3JzID0gNDMyNiksDQojICAgICAgICAgICJDOi9HSVMvTU9USVZBVEUvc2hhcGVmaWxlcy9yZXN1cnZfbm90X3ZhbF9FVU5JU19jb25mLnNocCIpDQpgYGANCg0KQ2hlY2tlZCBhbmQgeWVzDQoNCkhvdyBtYW55IHBvaW50cyB3aXRoIGRpZmZlcmVudGlhbCBHUFMgdGhhdCBoYXZlIGF0IGxlYXN0IDEgcnVsZSBicm9rZW4/DQoNCmBgYHtyfQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb25fdGVycmVzdHJpYWwgJT4lDQogIGRwbHlyOjpmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIikpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYXRhX3ZhbGlkYXRpb25fdGVycmVzdHJpYWwgJT4lDQojICAgICAgICAgICAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiAmDQojICAgICAgICAgICAgICAgICAgICAgdmFsaWRfMSA9PSAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIpICU+JQ0KIyAgICAgICAgICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxvbl91cGRhdGVkIiwgIkxhdF91cGRhdGVkIiksIGNycyA9IDQzMjYpLA0KIyAgICAgICAgICAiQzovR0lTL01PVElWQVRFL3NoYXBlZmlsZXMvcmVzdXJ2X25vdF92YWxfZGlmZl9HUFMuc2hwIikNCmBgYA0KDQojIE1hcHMNCg0KIyMgUG9pbnRzIEdQUw0KDQpgYGB7cn0NCiMgTG9hZCB3b3JsZCBib3VuZGFyaWVzDQp3b3JsZCA8LSBuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQ0KDQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19HUFNfZXh0ZW50IDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIpICU+JQ0KICBzdW1tYXJpc2UobG9uX21pbiA9IG1pbihMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxvbl9tYXggPSBtYXgoTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAyICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfR1BTX2V4dGVudCRsb25fbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX0dQU19leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19HUFNfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfR1BTX2V4dGVudCRsYXRfbWF4ICsgcGFkZGluZykNCg0KIyBDcmVhdGUgdGhlIHpvb21lZCBtYXANCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gd29ybGQsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiksDQogICAgICAgICAgICAgYWVzKHggPSBMb25fdXBkYXRlZCwgeSA9IExhdF91cGRhdGVkLCBjb2xvciA9IEVVTklTYV8xKSwNCiAgICAgICAgICAgICBzaXplID0gMSkgKw0KICBjb29yZF9zZih4bGltID0geF9saW1pdHMsIHlsaW0gPSB5X2xpbWl0cykgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpOdW1iZXIgb2YgR1BTIHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIpICU+JQ0KICBjb3VudChDb3VudHJ5KQ0KYGBgDQoNCiMjIFBvaW50cyBSZVN1cnZleQ0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHRoZSBleHRlbnQgb2YgdGhlIHBvaW50cw0KcG9pbnRzX3Jlc3VydmV5X2V4dGVudCA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIHN1bW1hcmlzZShsb25fbWluID0gbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbG9uX21heCA9IG1heChMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9taW4gPSBtaW4oTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWF4ID0gbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpKQ0KDQojIEFkZCBwYWRkaW5nIHRvIHRoZSBleHRlbnQgKGFkanVzdCBhcyBuZWVkZWQpDQpwYWRkaW5nIDwtIDIgICMgQWRqdXN0IHBhZGRpbmcgdG8geW91ciBwcmVmZXJlbmNlDQp4X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21heCArIHBhZGRpbmcpDQoNCiMgQ3JlYXRlIHRoZSB6b29tZWQgbWFwDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImdyYXkiKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApLA0KICAgICAgICAgICAgIGFlcyh4ID0gTG9uX3VwZGF0ZWQsIHkgPSBMYXRfdXBkYXRlZCwgY29sb3IgPSBFVU5JU2FfMSksDQogICAgICAgICAgICAgc2l6ZSA9IDEpICsNCiAgY29vcmRfc2YoeGxpbSA9IHhfbGltaXRzLCB5bGltID0geV9saW1pdHMpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KTnVtYmVyIG9mIFJlU3VydmV5IHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgY291bnQoQ291bnRyeSkNCmBgYA0KDQojIERpc3RyaWJ1dGlvbnMgZnJvbSBHUFMgcG9pbnRzIHdpdGhvdXQgcnVsZXMgYnJva2VuIHNvIGZhcg0KDQpDcmVhdGUgdGliYmxlIHdpdGggZGlmZmVyZW50aWFsIEdQUyBwb2ludHMgd2l0aG91dCBydWxlcyBicm9rZW4gc28gZmFyOg0KDQpgYGB7cn0NCmFsbF9HUFNfdmFsaWQgPC0gZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JQ0KICBkcGx5cjo6ZmlsdGVyKChgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8IA0KICAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiApICYNCiAgICAgICAgICAgdmFsaWRfMSA9PSAiTm8gcnVsZXMgYnJva2VuIHNvIGZhciIpIA0KYGBgDQoNCiMjIE5EVkksIE5ETUksIE5EV0ksIFNBVkkgYW5kIEVWSQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICBjKCJORFdJX21heCIsICJORFdJX3A5MCIsICJORFdJX21pbiIsICJORFdJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRXSSBtYXgiLCAiTkRXSSBwOTAiLCAiTkRXSSBtaW4iLCAiTkRXSSBwMTAiKSkNCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQgJT4lDQogICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVklfbWF4IDw9IDEpICU+JQ0KICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+PSAtMSAmIEVWSV9taW4gPD0gMSksDQogICAgICAgICAgIGMoIkVWSV9tYXgiLCAiRVZJX3A5MCIsICJFVklfbWluIiwgIkVWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIkVWSSBtYXgiLCAiRVZJIHA5MCIsICJFVkkgbWluIiwgIkVWSSBwMTAiKSkNCmBgYA0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQoNCiMjIFBoZW5vbG9neQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU09TX0RPWSIsIlBlYWtfRE9ZIiwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICJORFZJX2F0X1NPUyIsICJORFZJX2F0X1BlYWsiLCAiTkRWSV9hdF9FT1MiLA0KICAgICAgICAgICAgICJkaWZmX1BlYWtfU09TIiwiZGlmZl9QZWFrX0VPUyIsICJTZWFzb25fTGVuZ3RoIiksDQogICAgICAgICAgIGMoIlNPUyBET1kiLCAiUGVhayBET1kiLCAiRU9TIERPWSIsDQogICAgICAgICAgICAgIk5EVkkgYXQgU09TIiwgIk5EVkkgYXQgUGVhayIsICJORFZJIGF0IEVPUyIsDQogICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLUVPUyIsICJTZWFzb24gTGVuZ3RoIikpDQpgYGANCg0KIyBHUFMgdmFsaWQgcG9pbnRzIGFib3ZlIHAyMCBvZiBORFZJX21heCBhbmQgTkRNSV9taW4gZm9yIGVhY2ggaGFiaXRhdA0KDQojIEhFUkUhIA0KDQpDaG9zZW4gTkRWSV9taW4gYmVjYXVzZSBpdCB3YXMgaW1wb3J0YW50IGluIFJGIG1vZGVscywgYnV0IGxldCdzIHNlZSB3aXRoIG5ldyBkYXRhIQ0KDQpgYGB7cn0NCnBlcmNlbnRpbGVzX2FsbF9HUFMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUocGVyY2VudGlsZV8yMF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCBwcm9icyA9IDAuMjAsIG5hLnJtID0gVCksDQogICAgICAgICAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSkNCg0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXNfYWxsX0dQUywgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgbXV0YXRlKGNhdGVnb3J5X05EVklfbWF4ID0gY2FzZV93aGVuKA0KICAgIE5EVklfbWF4IDwgcGVyY2VudGlsZV8yMF9ORFZJX21heCB+ICJiZWxvd18yMHRoIiwNCiAgICBORFZJX21heCA+PSBwZXJjZW50aWxlXzIwX05EVklfbWF4IH4gImFib3ZlXzIwdGgiKSwNCiAgY2F0ZWdvcnlfTkRNSV9taW4gPSBjYXNlX3doZW4oDQogICAgTkRNSV9taW4gPCBwZXJjZW50aWxlXzIwX05ETUlfbWluIH4gImJlbG93XzIwdGgiLA0KICAgIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gfiAiYWJvdmVfMjB0aCIpKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5EVklfbWF4KSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORFZJX21heCksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5EVkkgbWF4IiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5ETUlfbWluKSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORE1JX21pbiksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5ETUkgbWluIiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMgUkYgbW9kZWxzDQoNClVzaW5nIHRoZSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdmVyc2lvbiBvZiByYW5kb20gZm9yZXN0IChjZm9yZXN0IGluIHBhY2thZ2UgcGFydHkpLiBTdWdnZXN0ZWQgaWYgdGhlIGRhdGEgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLiBDZm9yZXN0IGlzIG1vcmUgc3RhYmxlIGluIGRlcml2aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgdmFsdWVzIGluIHRoZSBwcmVzZW5jZSBvZiBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMsIHRodXMgcHJvdmlkaW5nIGJldHRlciBhY2N1cmFjeSBpbiBjYWxjdWxhdGluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIChyZWYgYmVsb3cpLg0KDQpIb3Rob3JuLCBULiwgSG9ybmlrLCBLLiBhbmQgWmVpbGVpcywgQS4gKDIwMDYpIFVuYmlhc2VkIFJlY3Vyc2l2ZSBQb3J0aW9uaW5nOiBBIENvbmRpdGlvbmFsIEluZmVyZW5jZSBGcmFtZXdvcmsuIEpvdXJuYWwgb2YgQ29tcHV0YXRpb25hbCBhbmQgR3JhcGhpY2FsIFN0YXRpc3RpY3MsIDE1LCA2NTEtDQo2NzQuIGh0dHA6Ly9keC5kb2kub3JnLzEwLjExOTgvMTA2MTg2MDA2WDEzMzkzMw0KDQojIyBBbGwgR1BTIHBvaW50cw0KDQpgYGB7cn0NCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGRwbHlyOjpmaWx0ZXJlZF9kYXRhMCksIDAuNyAqIG5yb3coZHBseXI6OmZpbHRlcmVkX2RhdGEwKSkNCnRyYWluX2RhdGEwIDwtIGRwbHlyOjpmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBkcGx5cjo6ZmlsdGVyZWRfZGF0YTBbLXRyYWluX2luZGljZXMwLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGRwbHlyOjpmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MCA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QwIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCwgdGVzdF9kYXRhMCRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MF9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDApDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QwX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGEwJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MwIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMA0KYGBgDQoNCiMjIFJFVklTRSBGUk9NIEhFUkU6IEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMA0KDQpkcGx5cjo6ZmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgYWJvdmUgcDIwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIHNlbGVjdCgtcGVyY2VudGlsZV8yMF9ORFZJX21heCwgLXBlcmNlbnRpbGVfMjBfTkRNSV9taW4pDQpgYGANCg0KYGBge3J9DQpwZXJjZW50aWxlcyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBwZXJjZW50aWxlXzIwX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuMjAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8yMF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjIwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfODBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC44MCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuODAsIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgcGVyY2VudGlsZXMgYmFjayB0byB0aGUgb3JpZ2luYWwgZGF0YQ0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBkcGx5cjo6ZmlsdGVyIHJvd3MgYWJvdmUgdGhlIDIwdGggcGVyY2VudGlsZSBmb3IgYm90aCB2YXJpYWJsZXMgZm9yIGVhY2ggY2F0ZWdvcnkgb2YgRVVOSVNhXzENCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMSA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKA0KICAgIE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggJiBORE1JX21pbiA+PSBwZXJjZW50aWxlXzIwX05ETUlfbWluDQogICAgKSAlPiUNCiAgZHBseXI6OmZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczEgPC0gc2FtcGxlKDE6bnJvdyhkcGx5cjo6ZmlsdGVyZWRfZGF0YTEpLCAwLjcgKiBucm93KGRwbHlyOjpmaWx0ZXJlZF9kYXRhMSkpDQp0cmFpbl9kYXRhMSA8LSBkcGx5cjo6ZmlsdGVyZWRfZGF0YTFbdHJhaW5faW5kaWNlczEsIF0NCnRlc3RfZGF0YTEgPC0gZHBseXI6OmZpbHRlcmVkX2RhdGExWy10cmFpbl9pbmRpY2VzMSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBkcGx5cjo6ZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpkcGx5cjo6ZmlsdGVyZWRfZGF0YTEgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCkludmVzdGlnYXRlIHBhY2thZ2UgZ2dwYXJ0eSAoZS5nLiBhdXRvcGxvdCBmdW5jdGlvbiwgYW5kIG1vcmUpLg0KDQpUTy1ETzogDQpDaG9vc2UgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgYmFzZWQgb24gdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAoSGFzdGllIGV0IGFsLiwgMjAwOSktDQoNCkhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuIFRoZSBlbGVtZW50cyBvZiBzdGF0aXN0aWNhbA0KbGVhcm5pbmc6IERhdGEgbWluaW5nLCBpbmZlcmVuY2UsIGFuZCBwcmVkaWN0aW9uLiBTcHJpbmdlciBTY2llbmNlICYNCkJ1c2luZXNzIE1lZGlhLg0KDQpNYXliZSBUT19ETzoNCldlIHZhcmlhdGVkIG50cmVlIGZyb20gNTAgdG8gODAwIGluIHN0ZXBzIG9mIDUwLCBsZWF2aW5nIG10cnkgY29uc3RhbnQgYXQgMi4gVGlzIHBhcmFtZXRlciB2YXJpYXRpb24gc2hvd2VkIHRoYXQgbnRyZWU9NTAwIHdhcyBvcHRpbWFsLCB3aGlsZSBoaWdoZXIgbnRyZWUgbGVkIHRvIG5vIGZ1cnRoZXIgbW9kZWwgaW1wcm92ZW1lbnQgKFN1cHBsZW1lbnRhcnkgRmlnLiBTMTApLiBTdWJzZXF1ZW50bHksIHRoZSBoeXBlcnBhcmFtZXRlciBtdHJ5IHdhcyB2YXJpZWQgZnJvbSAyIHRvIDggd2l0aCBjb25zdGFudCBudHJlZT01MDAuIEhlcmUsIG10cnk9MyBsZWQgdG8gdGhlIGJlc3QgcmVzdWx0cyBpbiBhbG1vc3QgYWxsIGNhc2VzIChTdXBwbGVtZW50YXJ5IEZpZy4gUzExKS4gQ29uc2VxdWVudGx5LCB3ZSBjaG9zZSBudHJlZT01MDAgYW5kIG10cnk9MyBmb3Igb3VyIG1haW4gYW5hbHlzaXMgYWNyb3NzIGFsbCBzdHVkeSBzaXRlcy4NCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QxIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSwgdGVzdF9kYXRhMSRFVU5JU2FfMSkNCmBgYA0KDQpTdXJyb2dhdGVUcmVlIC0tPiBkb2VzIG5vdCB3b3JrDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0MS5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDFfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClRyZWUgVmlzdWFsaXphdGlvbg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgc2luZ2xlIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB0cmVlIHVzaW5nIGN0cmVlDQpzaW5nbGVfdHJlZTEgPC0gY3RyZWUoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKyBORE1JX21pbiArDQogICAgICAgICAgICAgICAgICAgICAgIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsgRVZJX21pbiArIFNBVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LA0KICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExKQ0KDQojIFBsb3QgdGhlIHNpbmdsZSB0cmVlIHVzaW5nDQphdXRvcGxvdChzaW5nbGVfdHJlZTEpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBJUSByYW5nZQ0KDQpkcGx5cjo6ZmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIElRIHJhbmdlIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQpJUV9yYW5nZXMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgUTFfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC4yNSwgbmEucm0gPSBUKSwNCiAgICBRMV9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjI1LCBuYS5ybSA9IFQpLA0KICAgIFEzX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuNzUsIG5hLnJtID0gVCksDQogICAgUTNfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC43NSwgbmEucm0gPSBUKSwNCiAgICBJUVJfTkRWSV9tYXggPSBJUVIoTkRWSV9tYXgsIG5hLnJtID0gVFJVRSksDQogICAgSVFSX05ETUlfbWluID0gSVFSKE5ETUlfbWluLCBuYS5ybSA9IFRSVUUpDQogICAgKQ0KDQojIEpvaW4gdGhlIElRIHJhbmdlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihJUV9yYW5nZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIElRUiByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMiA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKA0KICAgIChORFZJX21heCA+PSBRMV9ORFZJX21heCAmIE5EVklfbWF4IDw9IFEzX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IFExX05ETUlfbWluICYgTkRNSV9taW4gPD0gUTNfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZHBseXI6OmZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczIgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMiksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTIpKQ0KdHJhaW5fZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbdHJhaW5faW5kaWNlczIsIF0NCnRlc3RfZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbLXRyYWluX2luZGljZXMyLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTIgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDIgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MiA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QyLCBuZXdkYXRhID0gdGVzdF9kYXRhMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDIsIHRlc3RfZGF0YTIkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDIgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MiwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDIgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MiwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MiwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QyLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDJfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QyKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0Ml9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMiA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzINCmBgYA0KDQojIyBBbGwgR1BTIHBvaW50cyB3aXRoaW4gMS41ICogSVEgcmFuZ2UNCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIDEuNSAqIElRIHJhbmdlIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQojIEZpbHRlciByb3dzIHdpdGhpbiB0aGUgMS41ICogSVFSIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSAoUTFfTkRWSV9tYXggLSAxLjUgKiBJUVJfTkRWSV9tYXgpICYgTkRWSV9tYXggPD0gKFEzX05EVklfbWF4ICsgMS41ICogSVFSX05EVklfbWF4KSkgJg0KICAgICAgKE5ETUlfbWluID49IChRMV9ORE1JX21pbiAtIDEuNSAqIElRUl9ORE1JX21pbikgJiBORE1JX21pbiA8PSAoUTNfTkRNSV9taW4gKyAxLjUgKiBJUVJfTkRNSV9taW4pKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMyA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEzKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMykpDQp0cmFpbl9kYXRhMyA8LSBmaWx0ZXJlZF9kYXRhM1t0cmFpbl9pbmRpY2VzMywgXQ0KdGVzdF9kYXRhMyA8LSBmaWx0ZXJlZF9kYXRhM1stdHJhaW5faW5kaWNlczMsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMyAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MyA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDMsIG5ld2RhdGEgPSB0ZXN0X2RhdGEzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MywgdGVzdF9kYXRhMyRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MyA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QzLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MyA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QzLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QzLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDMuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0M19kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDMpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QzX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGExJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MzIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMw0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBtZWFuICsvLSBTRA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyB3aXRoaW4gbWVhbiArLy0gU0Qgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCm1lYW5fc2QgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl9ORFZJX21heCA9IG1lYW4oYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBtZWFuX05ETUlfbWluID0gbWVhbihhbGxfR1BTX3ZhbGlkJE5ETUlfbWluLCBuYS5ybSA9IFQpLA0KICAgIHNkX05EVklfbWF4ID0gc2QoYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBzZF9ORE1JX21pbiA9IHNkKGFsbF9HUFNfdmFsaWQkTkRNSV9taW4sIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgSVEgcmFuZ2VzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGENCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKG1lYW5fc2QsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIHNwZWNpZmllZCByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGE0IDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gKG1lYW5fTkRWSV9tYXggLSBzZF9ORFZJX21heCkgJiBORFZJX21heCA8PSAobWVhbl9ORFZJX21heCArIHNkX05EVklfbWF4KSkgJg0KICAgICAgKE5ETUlfbWluID49IChtZWFuX05ETUlfbWluIC0gc2RfTkRNSV9taW4pICYgTkRNSV9taW4gPD0gKG1lYW5fTkRNSV9taW4gKyBzZF9ORE1JX21pbikpDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXM0IDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTQpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGE0KSkNCnRyYWluX2RhdGE0IDwtIGZpbHRlcmVkX2RhdGE0W3RyYWluX2luZGljZXM0LCBdDQp0ZXN0X2RhdGE0IDwtIGZpbHRlcmVkX2RhdGE0Wy10cmFpbl9pbmRpY2VzNCwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE0ICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3Q0IDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDQgPC0gcHJlZGljdChyZl9jZm9yZXN0NCwgbmV3ZGF0YSA9IHRlc3RfZGF0YTQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q0LCB0ZXN0X2RhdGE0JEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q0IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDQsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q0IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDQsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDQsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0NC5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q0X2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0NCkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDRfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzQgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2M0DQpgYGANCg0KIyMgQWxsIEdQUyBwb2ludHMgYWJvdmUgcDIwIGFuZCBiZWxvdyBwODANCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgYWJvdmUgcDIwIGFuZCBiZWxvdyBwODAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCiMgRmlsdGVyIHJvd3MgYWJvdmUgdGhlIDIwdGggcGVyY2VudGlsZSBhbmQgYmVsb3cgdGhlIDgwdGggcGVyY2VudGlsZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGE1IDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORFZJX21heCAmIE5EVklfbWF4IDw9IHBlcmNlbnRpbGVfODBfTkRWSV9tYXgpICYNCiAgICAoTkRNSV9taW4gPj0gcGVyY2VudGlsZV8yMF9ORE1JX21pbiAmIE5ETUlfbWluIDw9IHBlcmNlbnRpbGVfODBfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXM1IDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTUpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGE1KSkNCnRyYWluX2RhdGE1IDwtIGZpbHRlcmVkX2RhdGE1W3RyYWluX2luZGljZXM1LCBdDQp0ZXN0X2RhdGE1IDwtIGZpbHRlcmVkX2RhdGE1Wy10cmFpbl9pbmRpY2VzNSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE1ICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3Q1IDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDUgPC0gcHJlZGljdChyZl9jZm9yZXN0NSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q1LCB0ZXN0X2RhdGE1JEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q1IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDUsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q1IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDUsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDUsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0NS5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q1X2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0NSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDVfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzUgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2M1DQpgYGANCg0KIyBIRVJFOiBDb21wYXJlIFJGIDEtNQ0KDQojIENvcmRpbGxlcmEgZGF0YQ0KDQpgYGB7cn0NCkFscGluZUdyYXNzbGFuZHNfaW5kaWNlcyA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZF9TZW50aW5lbF9QbG90X0FsbHllYXJfQWxsbWV0cmljcy5jc3YiKQ0KQWxwaW5lR3Jhc3NsYW5kc19waGVuIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kc19QaGVub2xvZ3lfU09TX0VPU19QZWFrX05EVklfQW1wbGl0dWRlLmNzdiIpDQpBbHBpbmVHcmFzc2xhbmRzX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kc19DYW5vcHlIZWlnaHRfMW0uY3N2IikNClZlZ2V0YXRpb25UeXBlc19pbmRpY2VzIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfU2VudGluZWxfUGxvdF9BbGxZZWFyX0FsbG1ldHJpY3MuY3N2IikNClZlZ2V0YXRpb25UeXBlc19waGVuIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfUGhlbm9sb2d5X1NPU19FT1NfUGVha19ORFZJX0FtcGxpdHVkZS5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfQ2Fub3B5SGVpZ2h0XzFtLmNzdiIpDQpgYGANCg0KYGBge3J9DQpBbHBpbmVHcmFzc2xhbmRzIDwtIEFscGluZUdyYXNzbGFuZHNfaW5kaWNlcyAlPiUNCiAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpICU+JQ0KICByZW5hbWUoSMOhYml0YXQgPSAiSO+/vWJpdGF0IikgJT4lIA0KICBmdWxsX2pvaW4oQWxwaW5lR3Jhc3NsYW5kc19waGVuICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpICU+JQ0KICAgICAgICAgICAgICByZW5hbWUoSMOhYml0YXQgPSAiSO+/vWJpdGF0IikpICU+JQ0KICBmdWxsX2pvaW4oQWxwaW5lR3Jhc3NsYW5kc19DSCAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSkgJT4lDQogIHNlbGVjdCgtRGF0ZV9feWVhciwgLSBgUHJlY2lzae+/vW5gKSAlPiUNCiAgbXV0YXRlKERBVEUgPSB5bWQoREFURSkpICU+JQ0KICByZW5hbWUoSUQgPSAiUmVsZXZlX251bSIpICU+JQ0KICBtdXRhdGUoSUQgPSBhcy5jaGFyYWN0ZXIoSUQpKSAlPiUNCiAgbXV0YXRlKGxheWVyID0gIkFscGluZUdyYXNzbGFuZHMiKQ0KYGBgDQoNCmBgYHtyfQ0KVmVnZXRhdGlvblR5cGVzIDwtIFZlZ2V0YXRpb25UeXBlc19pbmRpY2VzICU+JQ0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykgJT4lDQogIGZ1bGxfam9pbihWZWdldGF0aW9uVHlwZXNfcGhlbiAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSkgJT4lDQogIGZ1bGxfam9pbihWZWdldGF0aW9uVHlwZXNfQ0ggICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykpICU+JQ0KICByZW5hbWUoSMOhYml0YXQgPSAiVFlQRSIpICU+JQ0KICBtdXRhdGUobGF5ZXIgPSAiVmVnZXRhdGlvblR5cGVzIikNCmBgYA0KDQpNZXJnZSBib3RoIGRhdGFzZXRzOg0KDQpgYGB7cn0NCmNvcmRpbGxlcmEgPC0gYmluZF9yb3dzKA0KICBBbHBpbmVHcmFzc2xhbmRzICU+JSBzZWxlY3QoREFURSwgSUQsIHN0YXJ0c193aXRoKCJORE1JIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydHNfd2l0aCgiTkRWSSIpLCBIw6FiaXRhdCwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlYWtfRE9ZIiwgIlNPU19ET1kiLCAiU2Vhc29uX0xlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2Fub3B5X2hlaWdodCIsICJsYXllciIpLA0KICBWZWdldGF0aW9uVHlwZXMgJT4lIHNlbGVjdChEQVRFLCBJRCwgc3RhcnRzX3dpdGgoIk5ETUkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIEjDoWJpdGF0LCAiRU9TX0RPWSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVha19ET1kiLCAiU09TX0RPWSIsICJTZWFzb25fTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW5vcHlfaGVpZ2h0IiwgImxheWVyIikNCiAgKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gY2FzZV93aGVuKA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlBhc3RpemFsfENlcnZ1bmFsfGdyYXNzbGFuZHxtZWFkb3ciKSB+ICJSIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJmb3Jlc3QiKSB+ICJUIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJTY3J1YnxzY3J1YnxTaHJ1YmxhbmR8c2hydWJsYW5kfHNocnVifEhlYXRobGFuZCIpIH4gIlMiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlN1ZWxvfFNjcmVlfHNjcmVlfGNsaWZmIikgfiAiVSIsDQogICAgSMOhYml0YXQgPSBpcy5uYShIw6FiaXRhdCkgfiAiUiIsDQogICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIEVVTklTYV8xX2Rlc2NyID0gY2FzZV93aGVuKA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlQiIH4gIkZvcmVzdHMgYW5kIG90aGVyIHdvb2RlZCBsYW5kIiwNCiAgICAgIEVVTklTYV8xID09ICJTIiB+ICJIZWF0aGxhbmRzLCBzY3J1YiBhbmQgdHVuZHJhIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIpDQogICAgKQ0KYGBgDQoNCiMjIE5EVkksIE5ETUkNCg0KYGBge3J9DQpkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgIGMoIk5EVklfbWF4IiwgIk5EVklfcDkwIiwgIk5EVklfbWluIiwgIk5EVklfcDEwIiksIA0KICAgICAgICAgICBjKCJORFZJIG1heCIsICJORFZJIHA5MCIsICJORFZJIG1pbiIsICJORFZJIHAxMCIpKQ0KZGlzdHJfcGxvdChjb3JkaWxsZXJhLA0KICAgICAgICAgICBjKCJORE1JX21heCIsICJORE1JX3A5MCIsICJORE1JX21pbiIsICJORE1JX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRNSSBtYXgiLCAiTkRNSSBwOTAiLCAiTkRNSSBtaW4iLCAiTkRNSSBwMTAiKSkNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=